package com.wingsofts.myapplication

import android.animation.Animator
import android.animation.ValueAnimator
import android.content.Context
import android.graphics.Camera
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Matrix
import android.util.AttributeSet
import android.util.DisplayMetrics
import android.view.MotionEvent
import android.view.ViewGroup

class ThreeDLayout(context: Context?, attrs: AttributeSet? = null) : ViewGroup(context, attrs) {
    private val mCamera: Camera
    private val mMatrix: Matrix

    //this viewgroup's center
    private var mCenterX = 0
    private var mCenterY = 0

    //rotateDegree
    private var mCanvasRotateY = 0f
    private var mCanvasRotateX = 0f
    private var mCanvasMaxRotateDegree = 50f
    private var mMode = MODE_BOTH_X_Y
    private val mDensity: Float
    private val mValues = FloatArray(9)

    //the flag of touch
    private var isCanTouch = false

    //the degree of animation
    private var mDegreeY = 0f
    private var mDegreeX = 0f

    //the flag of animate
    private var isPlaying = false

    //the degree of longer animate
    private var mLoopAnimateY = 0
    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        check(childCount == 1) { "ThreeDLayout can only have one child" }
        val child = getChildAt(0)
        measureChild(child, widthMeasureSpec, heightMeasureSpec)

        //only one child view,so give the same size
        setMeasuredDimension(child.measuredWidth, child.measuredHeight)
    }

    override fun onLayout(
        changed: Boolean,
        l: Int,
        t: Int,
        r: Int,
        b: Int
    ) {
        val child = getChildAt(0)
        child.layout(0, 0, child.measuredWidth, child.measuredHeight)
    }

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)
        mCenterX = w / 2
        mCenterY = h / 2
    }

    override fun onDraw(canvas: Canvas) {
        mMatrix.reset()
        mCamera.save()
        if (mMode == MODE_Y || mMode == MODE_BOTH_X_Y) {
            mCamera.rotateX(mCanvasRotateX)
        }
        if (mMode == MODE_X || mMode == MODE_BOTH_X_Y) {
            mCamera.rotateY(mCanvasRotateY)
        }
        mCamera.rotateY(mDegreeY)
        mCamera.rotateX(mDegreeX)
        if (isPlaying) {
            mCamera.rotateY(mLoopAnimateY++.toFloat())
            if (mLoopAnimateY == 360) {
                mLoopAnimateY = 0
            }
            invalidate()
        }
        mCamera.getMatrix(mMatrix)

        // fix the Camera bug,
        mMatrix.getValues(mValues)
        mValues[6] = mValues[6] / mDensity
        mValues[7] = mValues[7] / mDensity
        mMatrix.setValues(mValues)
        mCamera.restore()
        mMatrix.preTranslate(-mCenterX.toFloat(), -mCenterY.toFloat())
        mMatrix.postTranslate(mCenterX.toFloat(), mCenterY.toFloat())
        canvas.concat(mMatrix)
        super.onDraw(canvas)
    }

    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
        return if (isCanTouch) {
            true
        } else {
            super.onInterceptTouchEvent(ev)
        }
    }

    override fun onTouchEvent(event: MotionEvent): Boolean {
        return if (isCanTouch) {
            val x = event.x
            val y = event.y
            when (event.action) {
                MotionEvent.ACTION_MOVE -> {
                    rotateCanvasWhenMove(x, y)
                    invalidate()
                    return true
                }
                MotionEvent.ACTION_UP -> {
                    mDegreeY = 0f
                    rotateCanvasWhenMove(mCenterX.toFloat(), mCenterY.toFloat())
                    invalidate()
                    return true
                }
            }
            true
        } else {
            super.onTouchEvent(event)
        }
    }

    /**
     * get the value to rotate
     */
    private fun rotateCanvasWhenMove(x: Float, y: Float) {
        val dx = x - mCenterX
        val dy = y - mCenterY
        var percentX = dx / mCenterX
        var percentY = dy / mCenterY
        if (percentX > 1f) {
            percentX = 1f
        } else if (percentX < -1f) {
            percentX = -1f
        }
        if (percentY > 1f) {
            percentY = 1f
        } else if (percentY < -1f) {
            percentY = -1f
        }
        mCanvasRotateY = mCanvasMaxRotateDegree * percentX
        mCanvasRotateX = -(mCanvasMaxRotateDegree * percentY)
    }

    fun setTouchable(canTouch: Boolean) {
        isCanTouch = canTouch
    }

    fun setTouchMode(mode: Int) {
        mMode = mode
        isCanTouch = true
    }

    /**
     * set the max rotate degree
     */
    fun setMaxRotateDegree(degree: Int) {
        mCanvasMaxRotateDegree = degree.toFloat()
    }

    /**
     * start horizontal turn animate
     */
    fun startHorizontalAnimate(duration: Long) {
        val animator = ValueAnimator.ofFloat(-180f, 0f)
        animator.addUpdateListener { animation ->
            mDegreeY = animation.animatedValue as Float
            invalidate()
        }
        animator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator) {}
            override fun onAnimationEnd(animation: Animator) {
                mDegreeY = 0f
                animator.removeAllUpdateListeners()
            }

            override fun onAnimationCancel(animation: Animator) {}
            override fun onAnimationRepeat(animation: Animator) {}
        })
        animator.duration = duration
        animator.start()
    }

    /**
     * start horizontal turn animate delayed
     */
    fun startHorizontalAnimateDelayed(delayed: Long, duration: Long) {
        Thread(Runnable {
            try {
                Thread.sleep(delayed)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            post { startHorizontalAnimate(duration) }
        }).start()
    }

    /**
     * start vertical turn animate
     */
    fun startVerticalAnimate(duration: Long) {
        val animator = ValueAnimator.ofFloat(-180f, 0f)
        animator.addUpdateListener { animation ->
            mDegreeX = animation.animatedValue as Float
            invalidate()
        }
        animator.addListener(object : Animator.AnimatorListener {
            override fun onAnimationStart(animation: Animator) {}
            override fun onAnimationEnd(animation: Animator) {
                mDegreeX = 0f
                animator.removeAllUpdateListeners()
            }

            override fun onAnimationCancel(animation: Animator) {}
            override fun onAnimationRepeat(animation: Animator) {}
        })
        animator.duration = duration
        animator.start()
    }

    /**
     * start vertical turn animate delayed
     */
    fun startVerticalAnimateDelayed(delayed: Long, duration: Long) {
        Thread(Runnable {
            try {
                Thread.sleep(delayed)
            } catch (e: InterruptedException) {
                e.printStackTrace()
            }
            post { startVerticalAnimate(duration) }
        }).start()
    }

    /**
     * start loop animate
     */
    fun startHorizontalAnimate() {
        isPlaying = true
        invalidate()
    }

    /**
     * stop the loop animate
     */
    fun stopAnimate() {
        isPlaying = false
        mLoopAnimateY = 0
        invalidate()
    }

    companion object {
        //the touch mode
        var MODE_X = 0
        var MODE_Y = 1
        var MODE_BOTH_X_Y = 2
    }

    init {
        //set a default background to make sure onDraw() dispatch
        if (background == null) {
            setBackgroundColor(Color.parseColor("#ffffff"))
        }
        var dm = DisplayMetrics()
        dm = resources.displayMetrics
        mDensity = dm.density
        mCamera = Camera()
        mMatrix = Matrix()
    }
}